#!/usr/bin/perl

package MachineFinance;

use strict;
use warnings;

use Finance::Quote;
use Finance::QuoteHist;
use Finance::TA;
use DBI;
use FinanceUtils;
use Data::Dumper;

use Exporter;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);

$VERSION     = 1.00;
@ISA         = qw(Exporter);
@EXPORT      = (qw(&getState &getInds));
@EXPORT_OK   = ();

sub getState {
    my $machine = shift;
    &{\&{"getState$machine"}}(@_);
}

sub getStateTestMachine {
    my $oldState=shift;
    my $index = shift;
    my $prices = shift;
    my $indicators = shift;
    my $state = { id => "search", type => "no order" };
    my ( $upper, $lower , $lupper , $llower ) = ( undef, undef, undef, undef );
    my ( $supportPrice, $resistancePrice, $supportStrength, $resistanceStrength ) = ( 0 , 0 , 0 , 0 );
    for my $i ( 0 .. 20 ){
        if( $prices->{high}->[$index-$i] < $prices->{high}->[$index-$i-1] and $prices->{high}->[$index-$i-1] > $prices->{high}->[$index-$i-2] ){
            $resistancePrice=$prices->{high}->[$index-$i-1] unless defined $resistancePrice;
            if(abs($prices->{high}->[$index-$i-1]-$resistancePrice) < 0.5*$indicators->{atr}->[$index-1]){
                $resistanceStrength++;
            } elsif($prices->{high}->[$index-$i-1] > $resistancePrice){ $resistanceStrength--; }
        }
        if( $prices->{low}->[$index-$i] > $prices->{low}->[$index-$i-1] and $prices->{low}->[$index-$i-1] < $prices->{low}->[$index-$i-2] ){
            $supportPrice=$prices->{low}->[$index-$i-1] unless defined $supportPrice;
            if(abs($prices->{low}->[$index-$i-1]-$supportPrice) < 0.5*$indicators->{atr}->[$index-1]){
                $supportStrength++;
            } elsif($prices->{low}->[$index-$i-1] < $supportPrice){ $supportStrength--; }
        }
    }
    print "$prices->{name} $prices->{date}->[$index] $prices->{high}->[$index]/$prices->{low}->[$index]: Support at $supportPrice ($supportStrength), resistance at $resistancePrice ($resistanceStrength)";
    if( $oldState =~ /search/){
        if($resistanceStrength>1 and $prices->{close}->[$index]>$resistancePrice){
            $state->{order} = 1;
            $state->{id} = "set buy";
            $state->{type} = "buyAtStop";
            $state->{stopPrice} = $prices->{high}->[$index] + 0.1*$indicators->{atr}->[$index];
            $state->{slPrice} = $supportPrice;
            $state->{targetPrice} = $prices->{high}->[$index]+$indicators->{atr}->[$index];
        } if($supportStrength>1 and $prices->{close}->[$index]<$supportPrice){
            $state->{order} = 1;
            $state->{id} = "set sell";
            $state->{type} = "sellAtStop";
            $state->{stopPrice} = $prices->{low}->[$index] - 0.1*$indicators->{atr}->[$index];
            $state->{slPrice} = $resistancePrice;
            $state->{targetPrice} = $prices->{low}->[$index]-$indicators->{atr}->[$index];
        }
        $upper = $resistancePrice if $resistanceStrength>1;
        $lower = $supportPrice if $supportStrength>1;
        print " $state->{id} at $state->{stopPrice}" if defined $state->{stopPrice};
    } elsif( $oldState =~ /set buy|set sell/ ){
            $state->{id} = "ready" if $prices->{close}->[$index]<$resistancePrice and $prices->{close}->[$index]>$supportPrice;
            $state->{cancelPending} = 1 if $state->{id} eq "ready";
    }
    $indicators->{highLevel} = () unless exists $indicators->{highLevel};
    $indicators->{lowLevel} = () unless exists $indicators->{lowLevel};
    push @{$indicators->{highLevel}}, $upper;
    push @{$indicators->{lowLevel}}, $lower;
    $state->{changed}=1 if $oldState ne $state->{id};
    print "\n";
    return $state;
}

sub getStateMESAStochSineMachine {
    my $oldState=shift;
    my $index = shift;
    my $prices = shift;
    my $indicators = shift;
    push @{$indicators->{stopSeries}}, $indicators->{stop};
    if( $oldState eq "unknown" or $oldState eq "exit" ){
        return "readyToBuy"  if $indicators->{stoch}->[$index] < 20;
        return "readyToSell" if $indicators->{stoch}->[$index] > 80;
        return "unknown";
    }
    if( $oldState eq "readyToBuy" ){
        return "Buy" if $indicators->{mesa}->[$index] > 
                        ( $indicators->{mesa}->[$index-1] + $indicators->{atr}->[$index])
                     and $indicators->{sine}->[$index] > $indicators->{lead}->[$index];
        return "readyToBuy";
    }
    if( $oldState eq "readyToSell" ){
        return "Sell" if $indicators->{mesa}->[$index] < 
                        ( $indicators->{mesa}->[$index-1] - $indicators->{atr}->[$index])
                      and $indicators->{sine}->[$index] < $indicators->{lead}->[$index];
        return "readyToSell";
    }
    if( $oldState eq "Buy" or $oldState eq "holdBuy" ){
        return "holdBuy" if $indicators->{sine}->[$index] < $indicators->{lead}->[$index];
        return "exit";
    }
    if( $oldState eq "Sell" or $oldState eq "holdSell" ){
        return "holdSell" if $indicators->{sine}->[$index] > $indicators->{lead}->[$index];
        return "exit";
    }
}

sub getStateMESASineMachine {
    my $oldState=shift;
    my $index = shift;
    my $prices = shift;
    my $indicators = shift;
    push @{$indicators->{stopSeries}}, $indicators->{stop};
    if( $oldState eq "unknown" or $oldState eq "exit" ){
        return "readyToBuy"  if $indicators->{sine}->[$index] < $indicators->{lead}->[$index];
        return "readyToSell" if $indicators->{sine}->[$index] > $indicators->{lead}->[$index];
        return "unknown";
    }
    if( $oldState eq "readyToBuy" ){
        if($indicators->{mesa}->[$index] > ( $indicators->{mesa}->[$index-1] + $indicators->{atr}->[$index])){
            $indicators->{stop} = updateStop(undef, $indicators->{atr}->[$index], $prices->{close}->[$index], "long");
            return "Buy"
        }
        return "readyToBuy";
    }
    if( $oldState eq "readyToSell" ){
        if($indicators->{mesa}->[$index] < ( $indicators->{mesa}->[$index-1] - $indicators->{atr}->[$index])){
            $indicators->{stop} = updateStop(undef, $indicators->{atr}->[$index], $prices->{close}->[$index], "short");
            return "Sell" 
        }
        return "readyToSell";
    }
    if( $oldState eq "Buy" or $oldState eq "holdBuy" ){
        $indicators->{stop} = updateStop(undef, $indicators->{atr}->[$index], $prices->{close}->[$index], "long");
        return "holdBuy" if  $prices->{close}->[$index]>$indicators->{stop} and $indicators->{sine}->[$index] < $indicators->{lead}->[$index];
        return "exit";
    }
    if( $oldState eq "Sell" or $oldState eq "holdSell" ){
        $indicators->{stop} = updateStop(undef, $indicators->{atr}->[$index], $prices->{close}->[$index], "short");
        return "holdSell" if  $prices->{close}->[$index]<$indicators->{stop} and $indicators->{sine}->[$index] > $indicators->{lead}->[$index];
        return "exit";
    }
}

sub getStateStochSineMachine {
    my $oldState=shift;
    my $index = shift;
    my $prices = shift;
    my $indicators = shift;
    push @{$indicators->{stopSeries}}, $indicators->{stop};
    if( $oldState eq "unknown" or $oldState eq "exit" ){
        return "readyToBuy" if $indicators->{stoch}->[$index] < 20;
        return "readyToSell" if $indicators->{stoch}->[$index] > 80;
        return "unknown";
    }
    if( $oldState eq "readyToBuy" ){
        return "unknown" if $indicators->{stoch}->[$index] > 80;
        if($indicators->{sine}->[$index]<$indicators->{lead}->[$index]){
            $indicators->{stop} = updateStop(undef, $indicators->{atr}->[$index],
                                             $prices->{close}->[$index], "long");
            return "Buy"
        }
        return "readyToBuy";
    }
    if( $oldState eq "readyToSell" ){
        return "unknown" if $indicators->{stoch}->[$index] < 20;
        if($indicators->{sine}->[$index]>$indicators->{lead}->[$index]){
            $indicators->{stop} = updateStop(undef, $indicators->{atr}->[$index],
                                             $prices->{close}->[$index], "short");
            return "Sell"
        }
        return "readyToSell";
    }
    if( $oldState eq "Buy" or $oldState eq "holdBuy" ){
        $indicators->{stop} = updateStop($indicators->{stop}, $indicators->{atr}->[$index],
                                         $prices->{close}->[$index], "long");
        return "exit" if $prices->{close}->[$index]<$indicators->{stop} or $indicators->{stoch}->[$index]<20;
        return "holdBuy";
    }
    if( $oldState eq "Sell" or $oldState eq "holdSell" ){
        $indicators->{stop} = updateStop($indicators->{stop}, $indicators->{atr}->[$index],
                                         $prices->{close}->[$index], "short");
        return "exit" if $prices->{close}->[$index]>$indicators->{stop} or $indicators->{stoch}->[$index]>80;
        return "holdSell";
    }
}
 
sub getStateUOSineMachine {
    my $oldState=shift;
    my $index = shift;
    my $prices = shift;
    my $indicators = shift;
    if( $oldState eq "unknown" or $oldState eq "exit" ){
        return "readyToBuy" if $indicators->{stoch}->[$index] < 20;
        return "readyToSell" if $indicators->{stoch}->[$index] > 80;
        return "unknown";
    }
    if( $oldState eq "readyToBuy" ){
        return "unknown" if $indicators->{stoch}->[$index] > 80;
        if($indicators->{sine}->[$index]<$indicators->{lead}->[$index]){
            $indicators->{stop} = $prices->{close}->[$index]-$indicators->{atr}->[$index];
            return "Buy"
        }
        return "readyToBuy";
    }
    if( $oldState eq "readyToSell" ){
        return "unknown" if $indicators->{stoch}->[$index] < 20;
        if($indicators->{sine}->[$index]>$indicators->{lead}->[$index]){
            $indicators->{stop} = $prices->{close}->[$index]+$indicators->{atr}->[$index];
            return "Sell"
        }
        return "readyToSell";
    }
    if( $oldState eq "Buy" or $oldState eq "holdBuy" ){
        return "exit" if $prices->{close}->[$index]<$indicators->{stop} or $indicators->{stoch}<20;
        return "holdBuy";
    }
    if( $oldState eq "Sell" or $oldState eq "holdSell" ){
        return "exit" if $prices->{close}->[$index]>$indicators->{stop} or $indicators->{stoch}>80;
        return "holdSell";
    }
}
 
sub getInds {
    my $machine = shift;
    &{\&{"getInds$machine"}}(@_);
}

sub getIndsMESAStochSineMachine {
    my $dl = shift;
    my $prices = shift;
    my $tempInds = getIndicator({
           sine     => [ "TA_HT_SINE", [2,3], 0, $dl, $prices->{close} ],
           mesa     => [ "TA_MAMA", [2], 0 , $dl, $prices->{close}, ],
           stoch    => [ "TA_STOCH", [2], 0 , $dl, $prices->{high}, $prices->{low}, $prices->{close}, 3, 3, 2, 3, 2 ],
           atr      => [ "TA_ATR", [2], 0 , $dl, $prices->{high}, $prices->{low}, $prices->{close}, ]
    });
    my $indicators = { sine   => shift $tempInds->{sine},
                       lead   => shift $tempInds->{sine},
                       atr    => shift $tempInds->{atr},
                       stoch  => shift $tempInds->{stoch},
                       mesa   => shift $tempInds->{mesa} };
    my $min = $dl;
    for (keys %$indicators){
        $min = $#{$indicators->{$_}} if $#{$indicators->{$_}} < $min;
        print "Min set to $min. $_ length: $#{$indicators->{$_}}\n";
    }
    for (keys %$indicators){
        shift $indicators->{$_} while $#{$indicators->{$_}} > $min;
    }
    $indicators->{stop} = $prices->{close}->[0] unless defined $indicators->{stop};
    @{$indicators->{stopSeries}} = ();
    return ( $indicators, $min );
}

sub getIndsMESASineMachine {
    my $dl = shift;
    my $prices = shift;
    my $tempInds = getIndicator({
           sine     => [ "TA_HT_SINE", [2,3], 0, $dl, $prices->{close} ],
           mesa     => [ "TA_MAMA", [2], 0 , $dl, $prices->{close}, 0.1, 0.9 ],
           atr      => [ "TA_ATR", [2], 0 , $dl, $prices->{high}, $prices->{low}, $prices->{close}, ]
    });
    my $indicators = { sine   => shift $tempInds->{sine},
                       lead   => shift $tempInds->{sine},
                       atr    => shift $tempInds->{atr},
                       mesa   => shift $tempInds->{mesa} };
    my $min = $dl;
    for (keys %$indicators){
        $min = $#{$indicators->{$_}} if $#{$indicators->{$_}} < $min;
        print "Min set to $min. $_ length: $#{$indicators->{$_}}\n";
    }
    for (keys %$indicators){
        shift $indicators->{$_} while $#{$indicators->{$_}} > $min;
    }
    $indicators->{stop} = $prices->{close}->[0] unless defined $indicators->{stop};
    @{$indicators->{stopSeries}} = ();
    return ( $indicators, $min );
}

sub getIndsStochSineMachine {
    my $dl = shift;
    my $prices = shift;
    my $tempInds = getIndicator({
           sine     => [ "TA_HT_SINE", [2,3], 0, $dl, $prices->{close} ],
           stoch    => [ "TA_STOCH", [2], 0 , $dl, $prices->{high}, $prices->{low}, $prices->{close}, 3, 3, 2, 3, 2 ],
           atr      => [ "TA_ATR", [2], 0 , $dl, $prices->{high}, $prices->{low}, $prices->{close} ]
    });
    my $indicators = { sine   => shift $tempInds->{sine},
                       lead   => shift $tempInds->{sine},
                       atr    => shift $tempInds->{atr},
                       stoch  => shift $tempInds->{stoch} };
    my $min = $dl;
    for (keys %$indicators){
        $min = $#{$indicators->{$_}} if $#{$indicators->{$_}} < $min;
        print "Min set to $min. $_ length: $#{$indicators->{$_}}\n";
    }
    for (keys %$indicators){
        shift $indicators->{$_} while $#{$indicators->{$_}} > $min;
    }
    $indicators->{stop} = $prices->{close}->[0] unless defined $indicators->{stop};
    @{$indicators->{stopSeries}} = ();
    return ( $indicators, $min );
}

sub getIndsTestMachine {
    my $dl = shift;
    my $prices = shift;
    my $tempInds = getIndicator({ typical => [ "TA_TYPPRICE", [2], 0, $dl, $prices->{high}, $prices->{low}, $prices->{close} ] });
    my $indicators = { typical => shift $tempInds->{typical} };
    $tempInds = getIndicator({
           mama     => [ "TA_MAMA", [2, 3], 0, $dl, $indicators->{typical}, 0.5 , 0.05 ],
           lrslopeh => [ "TA_LINEARREG_SLOPE", [2], 0, $dl, $prices->{high}, 5 ],
           lrslopel => [ "TA_LINEARREG_SLOPE", [2], 0, $dl, $prices->{low}, 5 ],
           atr      => [ "TA_ATR", [2], 0, $dl, $prices->{high}, $prices->{low}, $prices->{close} ],
    });
    @$indicators{qw(mama fama lrslopeh lrslopel atr)} = ( shift $tempInds->{mama}, shift $tempInds->{mama}, 
                                                          shift $tempInds->{lrslopeh}, shift $tempInds->{lrslopel}, 
                                                          shift $tempInds->{atr} );
    for (@{$indicators->{atr}}){ $_ *= 0.5 }
    for (keys %$indicators){ unshift $indicators->{$_}, undef while $#{$indicators->{$_}}<$dl; }
    $tempInds = getIndicator({ 
           mamaLower => [ "TA_SUB", [2], 40, $dl, $indicators->{mama}, $indicators->{atr} ],
           mamaUpper => [ "TA_ADD", [2], 40, $dl, $indicators->{mama}, $indicators->{atr} ]
           });
    @$indicators{qw(mamaUpper mamaLower)} = ( shift $tempInds->{mamaUpper}, shift $tempInds->{mamaLower} );
    for (keys %$indicators){ unshift $indicators->{$_}, undef while $#{$indicators->{$_}}<$dl; }
    for (qw(mamaUpper mamaLower)){ unshift $indicators->{$_}, undef; }
    return $indicators;
}

return 1;
